package com.wechat.page.contact.list;

import java.util.List;
import java.util.Map;
import java.util.function.Consumer;

import org.springframework.beans.factory.annotation.Autowired;

import com.swak.entity.YesNo;
import com.swak.fx.support.AbstractPage;
import com.swak.fx.support.FXMLView;
import com.swak.fx.support.ui.UI;
import com.swak.fx.support.ui.UIScrollPane;
import com.swak.fx.support.ui.UIScrollPane.Loading;
import com.swak.ui.Event;
import com.swak.ui.EventListener;
import com.swak.utils.Lists;
import com.swak.utils.Maps;
import com.swak.utils.StringUtils;
import com.swak.utils.time.DateTimes;
import com.wechat.Cons;
import com.wechat.dto.ContactDTO;
import com.wechat.dto.ContactFindDTO;
import com.wechat.enums.ContactType;
import com.wechat.page.api.ContactApi;
import com.wechat.page.chat.message.MessageThumbComp;
import com.wechat.page.contact.card.ContactCardComp;
import com.wechat.page.contact.misc.ManageBtn;
import com.wechat.page.contact.misc.NewFriend;
import com.wechat.page.contact.misc.Title;

import javafx.beans.property.ListProperty;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleListProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.value.ObservableValue;
import javafx.collections.FXCollections;
import javafx.collections.ListChangeListener.Change;
import javafx.collections.ObservableList;
import javafx.geometry.Bounds;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Node;
import javafx.scene.control.ScrollPane.ScrollBarPolicy;
import javafx.scene.layout.Pane;
import javafx.scene.layout.StackPane;
import javafx.scene.layout.VBox;

/**
 * ListPage.
 * 
 * @author 618lf
 */
@FXMLView(title = "联系人列表", value = "ListPage.fxml", css = "ListPage.css")
public class ListPage extends AbstractPage implements EventListener {

	private VBox vbox;
	private UIScrollPane root;
	@Autowired
	private ContactApi contactApi;

	// 管理按钮
	private ManageBtn manageBtn;

	// 新的朋友
	private Title newFriendTitle;
	private NewFriend newFriend;

	// 加载中的组件
	private Loading loading;

	public ListPage() {
		this.vbox = new VBox();
		this.root = (UIScrollPane) super.root;
		this.loading = this.root.getLoading();
		this.manageBtn = new ManageBtn();
		this.newFriendTitle = new Title("新的朋友");
		this.newFriendTitle.nonBorderProperty().set(true);
		this.newFriend = NewFriend.New(NewFriend.class, this);
	}

	@Override
	public void initializeParts() {
		super.initializeParts();
		this.vbox.setAlignment(Pos.CENTER);
		this.vbox.getStyleClass().add("list-page__inner");
		this.vbox.getChildren().addAll(this.manageBtn, this.newFriendTitle, this.newFriend.getRoot());
		VBox.setMargin(this.manageBtn, new Insets(10, 10, 10, 12));
		this.root.setContent(vbox);
		this.root.setFitToWidth(true);
		this.root.setHbarPolicy(ScrollBarPolicy.NEVER);
		this.root.setVbarPolicy(ScrollBarPolicy.AS_NEEDED);

		// 设置数据
		this.newFriend.contactProperty().set(ContactDTO.of().setNickName("新的朋友"));
	}

	@Override
	public void setupEventHandlers() {
		super.setupEventHandlers();
	}

	@Override
	public void setupBindings() {
		super.setupBindings();
	}

	@Override
	public void setupValueChangedListeners() {
		super.setupValueChangedListeners();
		this.contactsProperty().addListener(this::onContactsChange);
		this.activeProperty().addListener(this::onContactActive);
		this.findProperty().addListener((ob, o, n) -> this.refresh());
	}

	private void onContactActive(ObservableValue<? extends ContactDTO> ob, ContactDTO unActive, ContactDTO active) {
		if (unActive != null) {
			Node remove = this.findContactNode(unActive);
			if (remove != null) {
				remove.getStyleClass().remove("active");
			}
		}
		if (active != null) {
			Node remove = this.findContactNode(active);
			if (remove != null) {
				remove.getStyleClass().add("active");
			}
		}
	}

	@SuppressWarnings("unchecked")
	private void onContactsChange(Change<? extends ContactDTO> c) {
		while (c.next()) {
			if (c.wasReplaced()) {
				List<ContactDTO> adds = (List<ContactDTO>) c.getAddedSubList();
				for (ContactDTO contact : adds) {
					this.onRemoveContact(contact);
					this.onAddContact(contact);
				}
			} else if (c.wasAdded()) {
				List<ContactDTO> adds = (List<ContactDTO>) c.getAddedSubList();
				for (ContactDTO contact : adds) {
					this.onAddContact(contact);
				}
			} else if (c.wasRemoved()) {
				List<ContactDTO> adds = (List<ContactDTO>) c.getRemoved();
				for (ContactDTO contact : adds) {
					this.onRemoveContact(contact);
				}
			}
		}
	}

	private void onAddContact(ContactDTO contact) {

		// 只能添加一次
		if (this.hasContact(contact)) {
			return;
		}

		// 实例化界面
		StackPane inner = new StackPane();
		inner.getStyleClass().add("list-page__ph");
		ContactCardComp cardThumb = ContactCardComp.New(ContactCardComp.class, this);
		cardThumb.$on(ContactCardComp.ClickEvent, onContactSelectConsumer);
		inner.getChildren().add(cardThumb.getRoot());
		inner.getProperties().put(UI.Conts.ID_Key, contact.getId());
		inner.getProperties().put(UI.Conts.Data_Key, contact);
		inner.getProperties().put(UI.Conts.Comp_Key, cardThumb);
		inner.getProperties().put(UI.Conts.Type_Key, this.getType(contact));

		// 保存列表实例
		this.saveContactNode(contact, inner);

		// 添加到列表
		this.Add2Box(inner);

		// 设置 Chat
		this.$runOnUI(() -> {
			cardThumb.contactProperty().set(contact);
		});
	}

	private String getType(ContactDTO contact) {
		if (contact.getType() != null && contact.getType() == ContactType.Assistant.getType()) {
			return "助手";
		} else if (contact.getType() != null && contact.getType() == ContactType.Chat_Room.getType()) {
			return "群聊";
		} else if (contact.getIsStar() != null && contact.getIsStar() == YesNo.Yes.getType()) {
			return "星标朋友";
		}
		return contact.getPyInitial();
	}

	private void Add2Box(Node node) {
		Object nodeType = node.getProperties().getOrDefault(UI.Conts.Type_Key, StringUtils.EMPTY);
		List<Node> nodes = this.vbox.getChildren();
		int index = -1;
		for (int i = 0; i < nodes.size(); i++) {
			Node child = nodes.get(i);
			Object childType = child.hasProperties() ? child.getProperties().get(UI.Conts.Type_Key) : StringUtils.EMPTY;
			if (nodeType != null && childType != null && childType.equals(nodeType)) {
				index = i;
				continue;
			}

			// find the last index
			if (index != -1) {
				break;
			}
		}

		// 没有找到相关的分类
		if (index == -1) {
			Title typeTitle = new Title(nodeType.toString());
			typeTitle.getProperties().put(UI.Conts.ID_Key, nodeType);
			typeTitle.getProperties().put(UI.Conts.Type_Key, nodeType);
			this.vbox.getChildren().add(typeTitle);
			this.vbox.getChildren().add(node);
		}

		// 找到位置
		else {
			this.vbox.getChildren().add(index, node);
		}
	}

	private void onRemoveContact(ContactDTO contact) {
		StackPane node = this.findContactNode(contact);
		if (node != null) {
			MessageThumbComp messageThumb = (MessageThumbComp) node.getProperties().get(UI.Conts.Comp_Key);
			messageThumb.destory();
			this.vbox.getChildren().remove(node);
		}
	}

	private void onContactSelect(Object o) {
		this.$post(Cons.Event_Contact_Select, (ContactDTO) o);
	}

	private Consumer<Object> onContactSelectConsumer = this::onContactSelect;

	// ================================================================================
	// Chat <==> Pane
	// ================================================================================
	private Map<String, StackPane> contactPanes = Maps.newHashMap();

	private synchronized boolean hasContact(ContactDTO chat) {
		return contactPanes.containsKey(chat.getId());
	}

	private synchronized void saveContactNode(ContactDTO chat, StackPane inner) {
		this.contactPanes.put(chat.getId(), inner);
	}

	private synchronized StackPane findContactNode(ContactDTO chat) {
		return this.contactPanes.get(chat.getId());
	}

	// ================================================================================
	// 异步加载数据 -- 直接使用 : activated 来刷新数据（只会执行一次）
	// ================================================================================
//	@Override
//	public void mounted() {
//		super.mounted();
//
//		this.$run(() -> this.load());
//	}
//
//	private void load() {
//		if (LOGGER.isDebugEnabled()) {
//			LOGGER.debug("[联系人列表] - 准备加载数据");
//		}
//		ContactFindDTO find = this.findProperty.get();
//		this.contactApi.friends(find).thenAccept(contacts -> {
//			if (LOGGER.isDebugEnabled()) {
//				LOGGER.debug("[联系人列表] - 加载数据:{}", contacts.size());
//			}
//			this.$runUI(() -> this.contactsProperty().addAll(contacts));
//		});
//	}

	// window.windowActivatedChange
	// 会触发activated.
	@Override
	public void activated() {
		super.activated();

		// 校验是否正在加载数据
		if (this.loading.isLoading()) {
			return;
		}

		if (LOGGER.isDebugEnabled()) {
			LOGGER.debug("[联系人列表] - 准备刷新数据");
		}

		// 开始加载数据
		if (this.loading.start()) {
			this.refresh();
		}
	}

	private void refresh() {
		if (LOGGER.isDebugEnabled()) {
			LOGGER.debug("[联系人列表] - 刷新数据");
		}
		this.$runContextCompleteWithUI(() -> this.contactApi.friends(new ContactFindDTO())) // 加载数据
				.thenAccept(contacts -> {
					if (LOGGER.isDebugEnabled()) {
						LOGGER.debug("[联系人列表] - 加载数据:{}", contacts.size());
					}
					// 数据加载结束
					this.loading.loaded();
					// 合并数据
					this.merge(contacts);
				});
	}

	private void merge(List<ContactDTO> contacts) {

		// 添加新添加的
		for (ContactDTO contact : contacts) {
			boolean found = this.contactsProperty().stream().filter(c -> c.getId().equals(contact.getId())).findFirst()
					.isPresent();
			if (!found) {
				this.contactsProperty().add(contact);
			}
		}

		// 删除已经删除的
		List<ContactDTO> deletes = Lists.newArrayList();
		this.contactsProperty().forEach(contact -> {
			boolean found = contacts.stream().filter(c -> c.getId().equals(contact.getId())).findFirst().isPresent();
			if (!found) {
				deletes.add(contact);
			}
		});
		for (ContactDTO contact : deletes) {
			this.contactsProperty().remove(contact);
		}
	}

	// ================================================================================
	// 事件监听
	// ================================================================================
	@Override
	public void listen(Event event) {

		if (LOGGER.isDebugEnabled()) {
			LOGGER.debug("[聊天列表] - 收到事件 ：{}", event);
		}

		// 刷新
		if (Cons.Event_Contact_Select.equals(event.getKey())) {
			this.$runUI(() -> this.activeProperty().set(event.getMessage()));
		} else if (Cons.Event_Contact_Refresh.equals(event.getKey())) {
			this.$runUI(() -> this.onContactRefresh(event.getMessage()));
		} else if (Cons.Event_Contact_Reload.equals(event.getKey())) {
			this.$runUI(() -> this.activated());
		}
	}

	private void onContactRefresh(ContactDTO updated) {
		StackPane node = this.findContactNode(updated);
		if (node != null) {
			ContactCardComp messageThumb = (ContactCardComp) node.getProperties().get(UI.Conts.Comp_Key);
			ContactDTO contact = (ContactDTO) node.getProperties().get(UI.Conts.Data_Key);
			contact.setPyInitial(updated.getPyInitial()).setPyAlias(updated.getPyAlias()).setAlias(updated.getAlias())
					.setRemarks(updated.getRemarks()).setUpdateDate(updated.getUpdateDate());
			messageThumb.refreshProperty().set(DateTimes.nowMilli());
		}
	}

	// ================================================================================
	// Recyclable
	// ================================================================================
	protected void setupRecyclableChatList() {
		this.root.vvalueProperty().addListener((ob, o, n) -> {
			this.layoutChatList();
		});
		this.root.viewportBoundsProperty().addListener((ob, o, n) -> {
			this.layoutChatList();
		});
	}

	protected void layoutChatList() {
		Node node = this.root.getContent();
		Bounds rootBounds = this.root.localToScene(this.root.getBoundsInLocal());
		if (node instanceof Pane) {
			ObservableList<Node> children = ((Pane) node).getChildren();
			for (int i = 0; i < children.size(); i++) {
				Node child = children.get(i);
				Bounds childbounds = child.localToScene(child.getBoundsInLocal());
				if (childbounds.getMinY() > rootBounds.getMaxY() || childbounds.getMaxY() < rootBounds.getMinY()) {
					child.setVisible(false);
				} else {
					child.setVisible(true);
				}
			}
		}
	}

	// ================================================================================
	// Properties
	// ================================================================================
	private final ListProperty<ContactDTO> contactsProperty = new SimpleListProperty<>(
			FXCollections.observableArrayList());

	public ListProperty<ContactDTO> contactsProperty() {
		return contactsProperty;
	}

	private final ObjectProperty<ContactDTO> activeProperty = new SimpleObjectProperty<>();

	public ObjectProperty<ContactDTO> activeProperty() {
		return activeProperty;
	}

	private final ObjectProperty<ContactFindDTO> findProperty = new SimpleObjectProperty<>();

	public ObjectProperty<ContactFindDTO> findProperty() {
		return findProperty;
	}
}
